#include <sys/ioctl.h>
#include <errno.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <linux/i2c.h>
#include <linux/i2c-dev.h>
#include <strings.h>	/* for strcasecmp() */
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <limits.h>
#include <dirent.h>
#include <fcntl.h>
#include <errno.h>

#define APPLICATION_PATH                "Application.bin"
#define UPGRADE_DATA_PACKAGES_LENGHT    0x40
#define UPGRADE_PACKAGES_LENGHT         0x40 + 0x04


typedef enum{

    MI_FALSE  =   0,
    MI_TRUE   =   1,

}MI_BOOL;

typedef unsigned char   MI_U8;
typedef unsigned short  MI_U16;

static int i2c_write(int fd,int slave_addr,unsigned char *p_data,unsigned short len);
static int i2c_read(int fd,int slave_addr,unsigned char *w_data,
							unsigned short w_len,unsigned char *p_data,unsigned short len);
MI_U8 get_ver_cmd[6]            = {0x01,0x02,0x00,0x00,0x00,0x00};
MI_U8 update_cmd[6]             = {0x02,0x02,0x00,0x00,0x00,0x00};
MI_U8 file_size_cmd[8]          = {0x03,0x04,0x00,0x00,0x00,0x00,0x00,0x00};
MI_U8 file_package[UPGRADE_PACKAGES_LENGHT]         = {0x04,UPGRADE_DATA_PACKAGES_LENGHT};
MI_U8 update_complete_cmd[6]    = {0x05,0x02,0x00,0x00};
MI_U16 w_num = 0;
char r_data[256] = {0};
#define I2C_DATA_LENGTH_MAX 132

#define MISSING_FUNC_FMT	"Error: Adapter does not have %s capability\n"

static MI_BOOL get_update_file_size(char * file_path,size_t *size)
{
    FILE *file;

    file = fopen(file_path,"rb");

    if (!file)
    {
        perror("get_update_file_size fopen error\n");
        return MI_FALSE;
    }
    fseek(file, 0L, SEEK_END);

    *size = ftell(file);

    fclose(file);
    return MI_TRUE;
}

static MI_U16 CRC16(MI_U8 * buf, MI_U16 len)
{
	MI_U16 i;
	MI_U16 crc = 0xffff;
 
	if (len == 0) {
		len = 1;
	}
	while (len--) {
        
		crc ^= *buf;
		for (i = 0; i<8; i++) 
		{            
			if (crc & 1) {               
				crc >>= 1;        
				crc ^= 0xA001;            
			}      
			else {               
				crc >>= 1;            
			}       
		}     
		buf++;
	}
	return(crc);
}

static MI_BOOL compare(MI_U8 *des,MI_U8 *src,int len)
{
    while (len--)
    {
        if (*des != *src)
        {
            return MI_FALSE;
        }

        des++;
        src++;
    }

    return MI_TRUE;
}

static void send_get_version_cmd(int fd,MI_U8 *p_data,MI_U16 r_len)
{
    int len = sizeof(get_ver_cmd);
    int crc = CRC16(get_ver_cmd,len-2);
    get_ver_cmd[len-2] = crc & 0x00ff;
    get_ver_cmd[len-1] = ((crc >> 8) & 0x00ff);
	i2c_read(fd,0x18,get_ver_cmd,len,p_data,r_len);
}

static void send_enter_update_cmd(int fd)
{
    int len = sizeof(update_cmd);
    int crc = CRC16(update_cmd,len-2);
    update_cmd[len-2] = crc & 0x00ff;
    update_cmd[len-1] = ((crc >> 8) & 0x00ff);
	i2c_write(fd,0x18,update_cmd,len);
}

static MI_BOOL send_update_file_size_cmd(int fd)
{
    int len = sizeof(file_size_cmd);
    size_t file_size = 0;
    get_update_file_size(APPLICATION_PATH,&file_size);

    file_size_cmd[2] = (file_size >> 24 & (0xff));
    file_size_cmd[3] = (file_size >> 16 & (0xff));
    file_size_cmd[4] = (file_size >> 8 & (0xff));
    file_size_cmd[5] = (file_size & (0xff));

    int crc = CRC16(file_size_cmd,len-2);
    file_size_cmd[len-2] = crc & 0x00ff;
    file_size_cmd[len-1] = ((crc >> 8) & 0x00ff);

	i2c_write(fd,0x18,file_size_cmd,len);
    return MI_TRUE;
}

static MI_BOOL send_file_every_package(int fd)
{
    int len = sizeof(file_package);
    FILE *fp;
    size_t file_size;
    int package_num;
    MI_U8 package_buff[UPGRADE_DATA_PACKAGES_LENGHT] = {0};

    fp = fopen(APPLICATION_PATH,"rb");
    if (!fp)
    {
        perror("fopen error\n");
        return MI_FALSE;
    }
    get_update_file_size(APPLICATION_PATH,&file_size);

    if (file_size % UPGRADE_DATA_PACKAGES_LENGHT == 0 )
    {
        package_num = file_size / UPGRADE_DATA_PACKAGES_LENGHT;
    }
    else
    {
        package_num = (file_size / UPGRADE_DATA_PACKAGES_LENGHT) + 1;
    }

    printf("pageage_num == %d\n",package_num);
    while (!feof(fp)/* condition */)
    {
        /* code */
        int r_len = fread(package_buff,1,UPGRADE_DATA_PACKAGES_LENGHT,fp);
        
        // 最后读出来不满128 ，用0xff补全。
        if (r_len != UPGRADE_DATA_PACKAGES_LENGHT)
        {
            for (int i=r_len;i<UPGRADE_DATA_PACKAGES_LENGHT;i++)
            {
                package_buff[i] = 0xff;
            }
        }

        memcpy(&file_package[2],package_buff,sizeof(package_buff));

        int crc = CRC16(file_package,sizeof(file_package)-2);
        file_package[sizeof(file_package)-2] = crc & 0x00ff;
        file_package[sizeof(file_package)-1] = ((crc >> 8) & 0x00ff);


        usleep(30 * 1000);
        w_num++;

        printf("send package process == [%03d]\n", ((w_num * 100)/package_num));

#if DEBUG
        for(int i=0;i< len;i++)
        {
            printf("0x%02x  ",file_package[i]);
            if ((i+1) % 16 == 0)
                printf("\n");
        }
        printf("\n");

#endif
        memset(r_data,0,sizeof(r_data));
        i2c_write(fd,0x18,file_package,len);
#if DEBUG
        // for(int i=0;i< len;i++)
        // {
        //     printf("0x%02x  ",r_data[i]);
        //     if ((i+1) % 16 == 0)
        //         printf("\n");
        // }
        // printf("\n");


        // int status = compare(r_data,file_package,20);
        // if (status)
        // {
        //     printf("send_file_every_package and receive cmd success!\n");
        // }
        // else
        // {
        //     perror("send_file_every_package not equal receive cmd\n");
        // }
       
        //printf("read len == %d  w_num == %d \n",len,w_num);
 #endif        
    }
    
    fclose(fp);
    return MI_TRUE;
}

static MI_BOOL send_update_complete_cmd(int fd)
{
    int len = sizeof(update_complete_cmd);
    int crc = CRC16(update_complete_cmd,len-2);
    update_complete_cmd[len-2] = crc & 0x00ff;
    update_complete_cmd[len-1] = ((crc >> 8) & 0x00ff);
    i2c_write(fd,0x18,update_complete_cmd,sizeof(update_complete_cmd));

    return MI_TRUE;
}

static void print_register_value(unsigned char  *p_buff,unsigned short len)
{
    for(int i=0;i<len;i++)
    {
        printf("0x%02x " , p_buff[i]);

        if ((i +1)% 16 == 0)
        {
            printf("\r\n");
        }
    }
    printf("\r\n");
}

int open_i2c_dev(int i2cbus, char *filename, size_t size, int quiet)
{
	int file, len;

	len = snprintf(filename, size, "/dev/i2c/%d", i2cbus);
	if (len >= (int)size) {
		fprintf(stderr, "%s: path truncated\n", filename);
		return -EOVERFLOW;
	}
	file = open(filename, O_RDWR);

	if (file < 0 && (errno == ENOENT || errno == ENOTDIR)) {
		len = snprintf(filename, size, "/dev/i2c-%d", i2cbus);
		if (len >= (int)size) {
			fprintf(stderr, "%s: path truncated\n", filename);
			return -EOVERFLOW;
		}
		file = open(filename, O_RDWR);
	}

	if (file < 0 && !quiet) {
		if (errno == ENOENT) {
			fprintf(stderr, "Error: Could not open file "
				"`/dev/i2c-%d' or `/dev/i2c/%d': %s\n",
				i2cbus, i2cbus, strerror(ENOENT));
		} else {
			fprintf(stderr, "Error: Could not open file "
				"`%s': %s\n", filename, strerror(errno));
			if (errno == EACCES)
				fprintf(stderr, "Run as root?\n");
		}
	}

	return file;
}

static int check_funcs(int file)
{
	unsigned long funcs;

	/* check adapter functionality */
	if (ioctl(file, I2C_FUNCS, &funcs) < 0) {
		fprintf(stderr, "Error: Could not get the adapter "
			"functionality matrix: %s\n", strerror(errno));
		return -1;
	}

	if (!(funcs & I2C_FUNC_I2C)) {
		fprintf(stderr, MISSING_FUNC_FMT, "I2C transfers");
		return -1;
	}

	return 0;
}

static int i2c_write(int fd,int slave_addr,unsigned char *p_data,unsigned short len)
{	
	struct i2c_rdwr_ioctl_data rdwr;
	int ret = 0;
 
	rdwr.nmsgs = 1;
	rdwr.msgs = (struct i2c_msg *)malloc(rdwr.nmsgs * sizeof(struct i2c_msg));
	if(!rdwr.msgs)
	{
		printf("msgs memery alloc error\n");
		close(fd);
		return -1;
	} 
	if ((rdwr.msgs[0].buf = (unsigned char *)malloc(len * sizeof(unsigned char))) == NULL)
	{
		printf("buf memery alloc error...\n");
		close(fd);
		return -1;
	}
 
	(rdwr.msgs[0]).len = len;
	(rdwr.msgs[0]).flags = !I2C_M_RD;
	(rdwr.msgs[0]).addr = slave_addr;

	memcpy((rdwr.msgs[0]).buf,p_data,len);
 
	rdwr.nmsgs = 1;
 
	ret = ioctl(fd, I2C_RDWR, (unsigned long) &rdwr);
	if(ret < 0)
	{
		printf("Error during I2C_RDWR ioctl with error code: %d\n", ret);
		return -1;
	}
	
	free(rdwr.msgs[0].buf);
	free(rdwr.msgs);

	return 0;
}

static int i2c_read(int fd,int slave_addr,unsigned char *w_data,
							unsigned short w_len,unsigned char *p_data,unsigned short len)
{
	struct i2c_rdwr_ioctl_data rdwr;
	unsigned char val;
	int ret;
 
	rdwr.nmsgs = 2;
	rdwr.msgs = (struct i2c_msg *)malloc(rdwr.nmsgs *sizeof(struct i2c_msg));
 
	if(!rdwr.msgs)
	{
		printf("Memery alloc error\n");
		close(fd);
		return -1;
	}

	if ((rdwr.msgs[0].buf = (unsigned char *)malloc(w_len * sizeof(unsigned char))) == NULL)
	{
		printf("buf memery alloc error...\n");
		close(fd);
		return -1;
	}

 
	(rdwr.msgs[0]).len = w_len;
	(rdwr.msgs[0]).flags = 0;
	(rdwr.msgs[0]).addr = slave_addr;
	memcpy((rdwr.msgs[0]).buf,w_data,w_len);
 
 	if ((rdwr.msgs[1].buf = (unsigned char *)malloc(len * sizeof(unsigned char))) == NULL)
	{
		printf("buf memery alloc error...\n");
		close(fd);
		return -1;
	}
	
	(rdwr.msgs[1]).len = len;
	(rdwr.msgs[1]).flags = 1;
	(rdwr.msgs[1]).addr = slave_addr;

	ret = ioctl(fd, I2C_RDWR, (unsigned long) &rdwr);
	if(ret < 0)
	{
		printf("Error during I2C_RDWR ioctl with error code: %d\n", ret);
		return -1;
	}
	memcpy(p_data,(rdwr.msgs[1]).buf,len);
 
 	free(rdwr.msgs[0].buf);
 	free(rdwr.msgs[1].buf);
	free(rdwr.msgs);
}

int main(int argc,char *argv[])
{
    int fd;
    char filename[20];
	size_t update_file_size;

    fd = open_i2c_dev(1, filename, sizeof(filename), 0);
	if (fd < 0 || check_funcs(fd))
		exit(1);

	memset(r_data,0,sizeof(r_data));
    send_get_version_cmd(fd,r_data,20);

    printf("get version == %s\n",r_data);
	usleep(30 * 1000);

	send_enter_update_cmd(fd);
	usleep(30 * 1000);

	get_update_file_size(APPLICATION_PATH,&update_file_size);
    printf("get update file size == %ld\n",update_file_size);
    send_update_file_size_cmd(fd);
	usleep(30 * 1000);
    send_file_every_package(fd);

    usleep(30 * 1000);
    memset(r_data,0,sizeof(r_data));
    send_update_complete_cmd(fd);

    close(fd);
    return 0;
}